package com.mathcat.mobile.android.net;

import java.net.URI;
import java.net.UnknownHostException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import android.util.Log;

import com.mathcat.mobile.android.util.Task;

/**
 * HttpTransaction.
 * 
 * @author Mathcat
 * @version 1.1.0, 2011.03.14
 */
public class HttpTransaction implements Task {

	// Canceling.
	// Error Handling.
	// 		4xx, 5xx Response; Timeout; Permission denied; Network IO Exception; 
	// Redirecting.
	// 		Auto redirecting.
	// Caching.
	// Proxy.
	// Check CMCC. ?
	
	private final static String LOG_TAG = "http";
	
	final static int STATUS_INITIALIZED = 1;
	final static int STATUS_WAITING = 2;
	final static int STATUS_EXECUTING = 3;
	final static int STATUS_COMPLETED = 4;
	final static int STATUS_FAILED = 5;
	final static int STATUS_CANCELED = 6;
	
	volatile int status;
	
	HttpRequestBase request;
	
	HttpCaller caller;
	
	HttpLoader loader;
	
	HttpClient httpClient;
	
	HttpTransaction(HttpRequestBase request, HttpCaller caller, HttpLoader loader) {
		this.request = request;
		this.caller = caller;
		this.loader = loader;
		
		this.status = STATUS_INITIALIZED;
	}
	
	@Override
	public void execute() {
		
		if (this.status == STATUS_CANCELED)
			return;

		init();
		
		try {
			
			HttpRequestBase request = this.request;
			
			Log.d(LOG_TAG, "Connecting to: " + this.request.getURI());
			this.status = STATUS_EXECUTING;
			
			HttpResponse response = httpClient.execute(request);
			
			if (this.status == STATUS_CANCELED)
				return;
			
			StatusLine statusLine = response.getStatusLine();
			int statusCode = statusLine.getStatusCode();
			
			switch (statusCode) {
				
				// 2xx Success.
				case HttpStatus.SC_OK:
					Log.d(LOG_TAG, "Response OK, request uri: " + this.request.getURI());
					
					if (HttpGet.METHOD_NAME.equals(request.getMethod()) && this.loader.useWaitingList) {
						URI uri = request.getURI();
						HttpTransactionWaitingList waitingList = this.loader.waitingList;
						HttpTransaction[] transactions = null;
						synchronized (waitingList) {
							transactions = waitingList.getAllByURI(uri);
						}
						if (transactions != null) {
							if(transactions.length > 1)
								Log.d(LOG_TAG, "Responding OK to all " + transactions.length + 
										" transactions in waiting list; request uri: " + this.request.getURI());
							
							for (HttpTransaction ht : transactions) {
								ht.status = STATUS_COMPLETED;
								// TODO: make a clone/copy of response? HttpResponse interface...
								if (ht.caller != null) {
									ht.caller.onCompelete(response);
								}
							}
						}
						
					} else {
						this.status = STATUS_COMPLETED;
						if (this.caller != null) {
							this.caller.onCompelete(response);
						}
					}
					
					break;
				
				// 3xx Redirecting.
				// 		Auto Redirecting?
				case HttpStatus.SC_SEE_OTHER:
				case HttpStatus.SC_MOVED_TEMPORARILY:
				case HttpStatus.SC_MOVED_PERMANENTLY:
					break;
				// 4xx Client Failed.
				// 5xx Server Failed.
				default:
					break;
			}
			
		} catch (UnknownHostException e) {
			Log.e(LOG_TAG, "Transaction failed, reason UNKNOWN_HOST; request uri: " + this.request.getURI());
			handleFailure(HttpCaller.ERROR_CODE_UNKNOWN_HOST);
		} catch (java.net.SocketTimeoutException e) {
			Log.e(LOG_TAG, "Transaction failed, reason TIMEOUT; request uri: " + this.request.getURI());
			handleFailure(HttpCaller.ERROR_CODE_TIMEOUT);
		} catch (org.apache.http.conn.ConnectTimeoutException e) {
			Log.e(LOG_TAG, "Transaction failed, reason TIMEOUT; request uri: " + this.request.getURI());
			handleFailure(HttpCaller.ERROR_CODE_TIMEOUT);
		} catch (Exception e) {
			e.printStackTrace();
			Log.e(LOG_TAG, "Transaction failed, reason UNKNOWN; request uri: " + this.request.getURI());
			handleFailure(HttpCaller.ERROR_CODE_UNKNOWN);
		}
	}

	@Override
	public void cancel() {
		if (this.status == STATUS_EXECUTING) {
			request.abort();
		}
		
		this.status = STATUS_CANCELED;
		
		if (HttpGet.METHOD_NAME.equals(request.getMethod()) && this.loader.useWaitingList) {
			URI uri = this.request.getURI();
			HttpTransactionWaitingList waitingList = loader.waitingList;
			HttpTransaction[] transactions = null;
			synchronized (waitingList) {
				transactions = waitingList.getAllByURI(uri);	
			}
			for (HttpTransaction ht : transactions) {
				HttpCaller caller = ht.caller;
				if (caller != null) {
					caller.onFailed(HttpCaller.ERROR_CODE_CANCELED);
				}
			}
		} else {
			this.status = HttpTransaction.STATUS_FAILED;
			if (this.caller != null) {
				caller.onFailed(HttpCaller.ERROR_CODE_CANCELED);
			}
		}
	}

	private void handleFailure(int errorCode) {
		if (HttpGet.METHOD_NAME.equals(request.getMethod()) && this.loader.useWaitingList) {
			URI uri = this.request.getURI();
			HttpTransactionWaitingList waitingList = loader.waitingList;
			HttpTransaction[] transactions = null;
			synchronized (waitingList) {
				transactions = waitingList.getAllByURI(uri);
			}
			for (HttpTransaction ht : transactions) {
				if(transactions.length > 1)
					Log.d(LOG_TAG, "Responding FAILED to all " + transactions.length + 
							" transactions in waiting list; request uri: " + this.request.getURI());
				
				HttpCaller caller = ht.caller;
				if (caller != null) {
					caller.onFailed(errorCode);
				}
			}
		} else {
			this.status = HttpTransaction.STATUS_FAILED;
			if (this.caller != null) {
				caller.onFailed(errorCode);
			}
		}
	}
	
	public void init() {
		// Initialize connection parameters.
		HttpParams params = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(params, loader.timeout);
		HttpConnectionParams.setSoTimeout(params, loader.timeout);
		HttpConnectionParams.setSocketBufferSize(params, 8912);
		httpClient = new DefaultHttpClient(params);
		
	}
}
